#include "gui.h"
#include "gui_entry.h"
#include <uview.h>
#include <uview_keycode.h>
#include <stdio.h>
#include <assert.h>
#include <stddef.h>

static void gui_entry_draw_cursor(gui_spirit_t *spirit)
{
    gui_entry_t *entry=GUI_ENTRY(spirit);
    if(!entry->focus)
        return ;
    if(!gui_entry_get_selection_bounds(entry,NULL,NULL))
    {
        int off_x=1,off_y=1;
        off_x+=entry->cursor_pos*8;
        dprintf("entry draw cursor: x %d y %d w %d h %d\n",off_x,off_y,3,spirit->height-2);
        gui_surface_rectfill(spirit->surface,off_x,off_y,3,spirit->height-2,GUI_BLACK);
    }
} 

static void gui_entry_draw_selection(gui_spirit_t *spirit)
{
    gui_entry_t *entry=GUI_ENTRY(spirit);
    if(!entry->focus)
        return ;
    if(gui_entry_get_selection_bounds(entry,NULL,NULL))
    {
        int chars=abs(entry->select_end_pos-entry->select_start_pos);
        if(!chars||chars>entry->spirit.max_text_len)
            return ;
        dprintf("entry select chars %d\n",chars);
        int min_pos=min(entry->select_start_pos,entry->select_end_pos);
        int w=chars*8;
        int h=spirit->height;

        int off_x=1,off_y=1;
        off_x+=min_pos*8;

        if(!entry->selection)
        {
            entry->selection=gui_surface_create(w,h);
            if(!entry->selection)
                return ;
        }
        else 
        {
            if(gui_surface_resize(entry->selection,w,h)<0)
                return ;
        }
        gui_surface_rectfill(entry->selection,0,0,entry->selection->w,entry->selection->h,GUI_GREEN);

        gui_rect_t srcrect={0,0,entry->selection->w,entry->selection->w};
        gui_rect_t dstrect={off_x,off_y,entry->selection->w,entry->selection->h};
        gui_surface_blit(entry->selection,&srcrect,spirit->surface,&dstrect);
    }
}

gui_spirit_t *gui_entry_create()
{
    gui_entry_t *entry = malloc(sizeof(gui_entry_t));
    if (!entry)
        return NULL;

    entry->editable = true;
    entry->visiable = true;
    entry->invisable_char = GUI_ENTRY_INVISABLE_CHAR_DEFAULT;
    entry->focus = false;
    entry->cursor_pos = 0;
    entry->focus_color = GUI_ENTRY_FOCUS_COLOR;
    entry->unfocus_color = GUI_GRAY;
    entry->select_end_pos = entry->select_start_pos = 0; // no select
    entry->start_selecting = 0;
    entry->selection = NULL;

    gui_spirit_t *spirit = &entry->spirit;
    gui_spirit_init(spirit, 0, 0, GUI_ENTRY_WIDTH_DEFAULT, GUI_ENTRY_HIEGHT_DEFAULT);
    gui_spirit_set_type(spirit, GUI_SPIRIT_TYPE_ENTRY);

    spirit->style.padding = 1;
    spirit->style.font_align = GUI_ALIGN_LEFT;
    spirit->style.background_color = GUI_WHITE;
    spirit->style.font_color = GUI_BLACK;
    spirit->style.font_align=GUI_ALIGN_LEFT;
    spirit->style.border_color = entry->unfocus_color;

    spirit->show_middle = gui_entry_draw_selection;
    spirit->show_bottom = gui_entry_draw_cursor;

    gui_spirit_set_text(spirit, " "); // set empty string

    assert(!gui_signal_create(spirit, "activate"));


    dprintf("[gui_entry] create entry at %x\n",entry);

    return spirit;
}

void gui_entry_set_max_length(gui_entry_t *entry, int max)
{
    if (entry)
        gui_spirit_set_text_max_len(&entry->spirit, max);
}

void gui_entry_set_text(gui_entry_t *entry,const char *text)
{
    if (entry)
    {
        gui_spirit_set_text(&entry->spirit, (char *)text);
        gui_spirit_show(&entry->spirit);
    }
}

const char *gui_entry_get_text(gui_entry_t *entry)
{
    if (!entry)
        return NULL;
    return entry->spirit.text;
}

void gui_entry_set_editable(gui_entry_t *entry, bool is_editable)
{
    entry->editable = is_editable;
}

bool gui_entry_get_editable(gui_entry_t *entry)
{
    return entry->editable;
}

void gui_entry_set_visiable(gui_entry_t *entry, bool is_visiable)
{
    entry->visiable = is_visiable;
    gui_spirit_t *spirit = &entry->spirit;
    if (is_visiable)
    {
        spirit->invisable_char = 0;
    }
    else
    {
        spirit->invisable_char = entry->invisable_char;
    }
}

bool gui_entry_get_visiable(gui_entry_t *entry)
{
    return entry->visiable;
}

void gui_entry_set_invisiable_char(gui_entry_t *entry, char ch)
{
    entry->invisable_char = ch;
    if (entry->visiable)
    {
        entry->spirit.invisable_char = entry->invisable_char;
    }
}

void gui_entry_unset_invisiable_char(gui_entry_t *entry)
{
    entry->invisable_char = GUI_ENTRY_INVISABLE_CHAR_DEFAULT;
}

void gui_entry_move_cursor(gui_entry_t *entry, gui_entry_cursor_orientation_t orientation, int step)
{
    if (!entry)
        return;
    switch (orientation)
    {
    case GUI_ENTRY_CURSOR_LEFT_TO_RIGHT:
        gui_entry_set_position(entry, entry->cursor_pos + step);
        break;
    case GUI_ENTRY_CURSOR_RIGHT_TO_LEFT:
        gui_entry_set_position(entry, entry->cursor_pos - step);
        break;
    default:
        break;
    }
    gui_spirit_show(&entry->spirit);
}

void gui_entry_set_focus(gui_entry_t *entry, bool is_focus)
{
    gui_spirit_t *spirit = &entry->spirit;
    if (is_focus)
    {
        entry->focus = true;
        spirit->style.border_color = entry->focus_color;
        gui_spirit_show(spirit);
    }
    else
    {
        if (entry->focus)
        {
            entry->focus = false;
            spirit->style.border_color = entry->unfocus_color;
            gui_spirit_show(spirit);
        }
        gui_entry_reset_selection(entry, entry->select_start_pos);
    }
}

bool gui_entry_get_focus(gui_entry_t *entry)
{
    return entry ? entry->focus : false;
}

int gui_entry_locate_position(gui_entry_t *entry, int position)
{
    gui_spirit_t *spirit = &entry->spirit;
    int cursor_pos = position - spirit->style.padding;
    int char_width = 8;
    cursor_pos += char_width / 2;
    cursor_pos /= char_width;
    gui_entry_set_position(entry, cursor_pos);
    return entry->cursor_pos;
}

void gui_entry_set_position(gui_entry_t *entry, int position)
{
    entry->cursor_pos = position;
    int lablen = gui_label_length(&entry->spirit);
    if (entry->cursor_pos > lablen)
    {
        entry->cursor_pos = lablen;
    }
    if (entry->spirit.max_text_len > 0 && entry->cursor_pos > entry->spirit.max_text_len)
    {
        entry->cursor_pos = entry->spirit.max_text_len;
    }
    if (entry->cursor_pos < 0)
    {
        entry->cursor_pos = 0;
    }
}

int gui_entry_get_position(gui_entry_t *entry)
{
    return entry ? entry->cursor_pos : 0;
}

void gui_entry_insert_text(gui_entry_t *entry, const char *new_text, int new_text_length)
{
    if (!entry)
        return;
    gui_spirit_t *spirit = &entry->spirit;
    char *p = spirit->text;
    if (p)
    {
        // insert length check
        if (spirit->max_text_len > 0 && new_text_length + gui_label_length(spirit) > spirit->max_text_len)
            return 0;

        // create a temp text buffer
        char strbuff[256]={0};
        //copy char before cursor
        strncpy(strbuff, p, entry->cursor_pos);
        //insert new string
        strcat(strbuff, new_text);
        //copy less char after cursor
        p += entry->cursor_pos;
        strcat(strbuff, p);

        gui_entry_set_text(entry, strbuff);
        gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_LEFT_TO_RIGHT, new_text_length);
    }
    else
    {
        gui_entry_set_text(entry, new_text);
        gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_LEFT_TO_RIGHT, new_text_length);
    }
}

void gui_entry_delete_text(gui_entry_t *entry, int start_pos, int end_pos)
{
    if (!entry)
        return;
    gui_spirit_t *spirit = &entry->spirit;
    assert(spirit);
    char *p = spirit->text;
    if (p)
    {
        int old_len=0;
        // start cursor position invalid
        if (start_pos < 0)
            return;

        //get src text length 
        old_len = gui_label_length(spirit);
        //default delect all text
        if (end_pos < 0)
            end_pos = old_len;
        if (start_pos < 0)
            end_pos = 0;

        if (end_pos > old_len)
            end_pos = old_len;
        if (start_pos > old_len)
            start_pos = old_len;
                    
        char strbuff[256];
    
        // copy char before start pos
        if (start_pos >= 0)
        {
            strncpy(strbuff, p, start_pos);
        }

        // copy char after end pos
        p += end_pos;
        strcat(strbuff, p);

        gui_entry_set_position(entry, start_pos);
        gui_entry_set_text(entry, strbuff);
    }
}

void gui_entry_process_key(gui_entry_t *entry, int keycode, int modify)
{
    if (entry->editable == false)
        return;
    gui_spirit_t *spirit = &entry->spirit;

    int start_pos, end_pos;
    switch (keycode)
    {
    case UVIEW_KEY_UP:
        break;
    case UVIEW_KEY_DOWN:
        break;
    case UVIEW_KEY_LEFT:
        // ctrl+shift select all after cursor pos
        if (modify & (UVIEW_KMOD_CTRL) && (modify & (UVIEW_KMOD_SHIFT))) // select move to start
        {
            if (!gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
            {
                end_pos = entry->cursor_pos;
            }
            gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_RIGHT_TO_LEFT, entry->cursor_pos);
            if (end_pos != entry->cursor_pos)
            {
                gui_entry_select_region(entry, entry->cursor_pos, end_pos);
                gui_spirit_show(spirit);
            }
            entry->start_selecting = 1;
        }
        else
        {
            if (gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
            {
                if (modify & UVIEW_KMOD_SHIFT)
                {
                    int pos = gui_entry_get_position(entry);
                    gui_entry_set_position(entry, pos - 1);
                    int new_pos = gui_entry_get_position(entry);
                    if (pos != new_pos)
                    {
                        if (pos < new_pos)
                        {
                            entry->select_start_pos = new_pos;
                        }
                        else
                        {
                            entry->select_end_pos = new_pos;
                        }
 
                        gui_spirit_show(spirit);
                    }
                    else
                    {
                        gui_entry_reset_selection(entry, start_pos);
                        gui_entry_set_position(entry, start_pos);
                    }
                }
            }
            else
            {
                if (modify & UVIEW_KMOD_CTRL) // move to start
                {
                    gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_RIGHT_TO_LEFT, entry->cursor_pos);
                }
                else
                {
                    if (modify & UVIEW_KMOD_SHIFT) // selection
                    {
                        entry->start_selecting = 1;
                        gui_entry_select_region(entry, entry->cursor_pos - 1, entry->cursor_pos);
                        gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_RIGHT_TO_LEFT, 1);
                        gui_spirit_show(spirit);
                    }
                }
            }
        }
        break;
    case UVIEW_KEY_RIGHT:
        // ctrl & shift  select all after cursor position
        if (modify & UVIEW_KMOD_CTRL && modify & UVIEW_KMOD_SHIFT) // selection move to ended
        {
            if (!gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
            {
                start_pos = entry->cursor_pos; // right selection
            }
            gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_LEFT_TO_RIGHT, gui_label_create(spirit));
            if (start_pos != entry->cursor_pos)
            {
                gui_entry_select_region(entry, start_pos, entry->cursor_pos);
                gui_spirit_show(spirit);
            }
            entry->start_selecting = 1;
        }
        else
        {
            if (gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
            {
                // had selection
                if (modify & UVIEW_KMOD_SHIFT)
                {
                    int pos = gui_entry_get_position(entry);
                    gui_entry_set_position(entry, pos + 1);
                    int new_pos = gui_entry_get_position(entry);
                    if (pos != new_pos)
                    {
                        if (pos < end_pos)
                            entry->select_start_pos = new_pos;
                        else if (pos > start_pos)
                            entry->select_end_pos = end_pos;
                        gui_spirit_show(&entry->spirit);
                    }
                }
                else
                {
                    gui_entry_reset_selection(entry, end_pos);
                    gui_entry_set_position(entry, end_pos);
                }
                gui_spirit_show(spirit);
            }
            else
            {
                if (modify & UVIEW_KMOD_CTRL) // move to ended
                    gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_LEFT_TO_RIGHT, gui_label_length(spirit));
                else
                {
                    if (modify & UVIEW_KMOD_SHIFT)
                    {
                        entry->start_selecting = 1;
                        gui_entry_select_region(entry, entry->cursor_pos, entry->cursor_pos + 1);
                        gui_entry_move_cursor(entry, GUI_ENTRY_CURSOR_LEFT_TO_RIGHT, 1);
                    }
                }
            }
        }
        break;
    case UVIEW_KEY_ENTER: // finish input
        gui_entry_set_focus(entry, false);
        gui_signal_emit_by_name(spirit, "activate");
        break;
    case UVIEW_KEY_BACKSPACE:
        if (gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
        {
            gui_entry_delete_text(entry, start_pos, end_pos);   
            gui_entry_reset_selection(entry, start_pos);
        }
        else
        {
            gui_entry_delete_text(entry, entry->cursor_pos - 1, entry->cursor_pos);
        }
        break;
    default:
    {
        if (gui_entry_get_selection_bounds(entry, &start_pos, &end_pos))
        {
            gui_entry_delete_text(entry, start_pos, end_pos);   
            gui_entry_reset_selection(entry, start_pos);
        }
        char s[2] = {keycode,0};
        gui_entry_insert_text(entry, s, 1);
    }
    break;
    }
}

void gui_entry_select_region(gui_entry_t *entry,int start_pos,int end_pos)
{
    if(!entry)
        return ;
    entry->select_start_pos=start_pos;
    if(entry->select_start_pos<0)
        entry->select_start_pos=0;
    entry->select_end_pos=end_pos;
    if(entry->select_end_pos<0)
        entry->select_end_pos=0;
}

bool gui_entry_get_selection_bounds(gui_entry_t *entry,int *start_pos,int *end_pos)
{
    if(!entry)
        return false;
    if(entry->select_start_pos==entry->select_end_pos)
        return false;
    if(start_pos)
        *start_pos=entry->select_start_pos;
    if(end_pos)
        *end_pos=entry->select_end_pos;
    return true;
}

//reset selection 
void gui_entry_reset_selection(gui_entry_t *entry,int position)
{
    gui_entry_select_region(entry,position,position);
    entry->start_selecting=0;
}

void gui_entry_mouse_motion(gui_entry_t *entry,int position)
{
    if(gui_entry_get_focus(entry))
    {
        if(entry->start_selecting)
        {
            int pos=gui_entry_get_position(entry);
            int new_pos=gui_entry_locate_position(entry,position);
            if(pos!=new_pos)
            {
                entry->select_end_pos=new_pos;
                gui_spirit_show(&entry->spirit);
            }
        }
    }
}

void gui_entry_mouse_press(gui_entry_t *entry,int position)
{
    int pos=gui_entry_locate_position(entry,position);
    if(!entry->start_selecting)
    {
        entry->select_start_pos=pos;
    }
    else 
    {
        gui_entry_reset_selection(entry,pos);
    }
    gui_entry_set_position(entry,pos);
    gui_spirit_show(&entry->spirit);
    entry->start_selecting=1;
}

void gui_entry_mouse_release(gui_entry_t *entry)
{
    if(gui_entry_get_focus(entry))
    {
        if(entry->start_selecting)
        {
            entry->select_end_pos=gui_entry_get_position(entry);
            if(entry->select_end_pos<entry->select_start_pos)
            {
                int tmp_pos=entry->select_end_pos;
                entry->select_end_pos=entry->select_start_pos;
                entry->select_start_pos=tmp_pos; 
            }
            entry->start_selecting=0;
            gui_spirit_show(&entry->spirit);
        }
    }
}